﻿using Alternet.Scripter;
using Alternet.Syntax.Parsers.Roslyn;
using OrBitDataModule.Base;
using OrBitDataModule.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace OrBitDataModule.Common
{
    public class ScriptRunner
    {
        private string scriptCpde;

        public string ScriptCpde { get => scriptCpde; set => scriptCpde = value; }

        /// <summary>
        /// 脚本编译完成事件
        /// </summary>
        public event EventHandler OnScriptCompiled;

        private ScriptRun scriptRun = null;

        /// <summary>
        /// 是否可运行脚本
        /// </summary>
        public bool CanExcute { get; private set; } = false;

        /// <summary>
        /// 是否已编译
        /// </summary>
        public bool Compiled { get => scriptRun.Compiled; }

        public ScriptBase Script { get; private set; }
        public ScriptRunner()
        {
            scriptRun = new ScriptRun();
            scriptRun.ScriptSource.WithDefaultReferences();
            scriptRun.ScriptCompiled += ScriptRun_ScriptCompiled;
        }

        private void ScriptRun_ScriptCompiled(object sender, EventArgs e) => OnScriptCompiled?.Invoke(sender, e);
        private void InitScriptRun()
        {
            scriptRun.AssemblyKind = ScriptAssemblyKind.DynamicLibrary;
            scriptRun.ScriptLanguage = ScriptLanguage.CSharp;
            scriptRun.ScriptMode = ScriptMode.Release;
            //默认添加数据模块dll的引用
            AddReference(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "OrBitDataModule.dll"));
            AddReference("System.Data.dll");
            AddReference("System.Windows.Forms.dll");
            AddLocalReference();
        }

        /// <summary>
        /// 添加dll引用，CLR直接文件名，第三方要路径
        /// </summary>
        /// <param name="dllFilePath"></param>
        public void AddReference(string dllFilePath)
        {
            scriptRun.ScriptSource.References.Add(dllFilePath);
        }

        /// <summary>
        /// 添加本地程序目录所有dll
        /// </summary>
        private void AddLocalReference()
        {
            string directory = AppDomain.CurrentDomain.BaseDirectory;
            foreach (string file in Directory.GetFiles(directory, "*.dll"))
            {
                try
                {
                    if (Path.GetFileName(file).StartsWith("System.") && Path.GetFileName(file) != "System.EnterpriseServices.Wrapper.dll")
                    {
                        AddReference(Path.GetFileName(file));
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"加载 {Path.GetFileName(file)} 时出错：{ex.Message}");
                }
            }
        }


        /// <summary>
        /// 编译脚本
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public (bool Result, string Message) CompileScript(IContext context)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(scriptCpde))
                {
                    CanExcute = false;
                    return (false, "empty code");
                }
                scriptRun.Reset();
                InitScriptRun();
                scriptRun.ScriptSource.FromScriptCode(this.scriptCpde);
                scriptRun.Compile();
                if (scriptRun.ScriptHost.CompilerErrors.Count() == 0)
                {
                    //初始化对象内部的Context
                    Assembly assembly = scriptRun.ScriptHost.ScriptAssembly;

                    if (assembly == null)
                    {
                        CanExcute = false;
                        return (false, "assembly is null");
                    }

                    Type type = FindDataBaseType(assembly, "ScriptBase");
                    Script = Activator.CreateInstance(type) as ScriptBase;
                    type.BaseType.InvokeMember("Init", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, Script, new object[] { context });
                    CanExcute = true;
                    return (true, "");
                }
                else
                {
                    CanExcute = false;
                    return (false, string.Join("\r\n", scriptRun.ScriptHost.CompilerErrors.Select(x => x.ToString()).ToArray()));
                }
            }
            catch (Exception ex)
            {
                CanExcute = false;
                return (false, ex.Message);
            }
        }
        private Type FindDataBaseType(Assembly assembly, string baseTypeName) => assembly.GetTypes().FirstOrDefault(t => !t.IsAbstract && t.IsClass && t.BaseType != null && t.BaseType.Name == baseTypeName);

    }
}
